/*******************************************************************************
 * Copyright 2013 Mindaugas Idzelis
 * 
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * 
 *   http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 ******************************************************************************/
package incontrol.server;

import static com.google.common.base.Predicates.in;
import static com.google.common.base.Predicates.not;
import static com.google.common.collect.Iterables.filter;
import static com.google.common.collect.Lists.newArrayListWithExpectedSize;
import static java.util.Collections.singleton;
import incontrol.bf3.protocol.events.PlayerLeaveEvent;
import incontrol.bf3.protocol.events.PlayerSquadChangeEvent;
import incontrol.bf3.protocol.events.PlayerTeamChangeEvent;
import incontrol.bf3.protocol.events.ServerEvent;
import incontrol.bf3.protocol.events.datatype.PlayerInfo;
import incontrol.bf3.protocol.events.datatype.PlayerSet;
import incontrol.bf3.protocol.events.datatype.ServerInfo;
import incontrol.bf3.protocol.requests.ListPlayersRequest;
import incontrol.bf3.protocol.requests.ServerInfoRequest;
import incontrol.bf3.protocol.responses.ListPlayersResponse;
import incontrol.bf3.protocol.responses.Response;
import incontrol.bf3.protocol.responses.ServerInfoResponse;
import incontrol.bf3.state.PlayerList;
import incontrol.bf3.state.PlayerState;
import incontrol.bf3.state.ServerState;
import incontrol.core.protocol.events.StatsRefreshedEvent;
import incontrol.core.protocol.events.StatsRefreshedEvent.RefreshType;
import incontrol.rcon.protocol.RCONClient;

import java.io.IOException;
import java.util.List;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;

import org.apache.commons.logging.LogFactory;

import com.google.common.collect.ImmutableList;
import com.google.common.util.concurrent.Futures;
import com.google.common.util.concurrent.Uninterruptibles;

public class StateUpdater {

	private ScheduledExecutorService scheduler = Executors
			.newScheduledThreadPool(1);

	private Object sync = new Object();
	private ServerState serverState;

	private EventManager eventManager;

	public StateUpdater(ServerState serverState, EventManager events) {
		this.serverState = serverState;
		this.eventManager = events;
	}

	public void internalPost(ServerEvent event) {
		if (event instanceof PlayerTeamChangeEvent) {

			boolean changed = false;
			synchronized (sync) {
				ServerState state = serverState.freeze();
				String player = ((PlayerTeamChangeEvent) event).getPlayerName();
				PlayerState old = serverState.getPlayerList().byName()
						.get(player);
				if (old != null) {
					PlayerState newps = old
							.setTeam(((PlayerTeamChangeEvent) event).getTeam());

					Iterable<PlayerState> filtered = filter(serverState
							.getPlayerList().getPlayers(),
							not(in(singleton(old))));
					ImmutableList<PlayerState> build = new ImmutableList.Builder<PlayerState>()
							.addAll(filtered).add(newps).build();

					serverState.setPlayerList(new PlayerList(build));
				}
				changed = !serverState.equals(state);
			}
			if (changed) {
				eventManager.sendEvent(new StatsRefreshedEvent(
						RefreshType.EVENT));
			}
		} else if (event instanceof PlayerSquadChangeEvent) {

			boolean changed = false;
			synchronized (sync) {
				ServerState state = serverState.freeze();
				String player = ((PlayerSquadChangeEvent) event)
						.getPlayerName();
				PlayerState old = serverState.getPlayerList().byName()
						.get(player);
				if (old != null) {
					PlayerState newps = old
							.setTeam(((PlayerSquadChangeEvent) event).getTeam());

					Iterable<PlayerState> filtered = filter(serverState
							.getPlayerList().getPlayers(),
							not(in(singleton(old))));
					ImmutableList<PlayerState> build = new ImmutableList.Builder<PlayerState>()
							.addAll(filtered).add(newps).build();

					serverState.setPlayerList(new PlayerList(build));
				}
				changed = !serverState.equals(state);
			}
			if (changed) {
				eventManager.sendEvent(new StatsRefreshedEvent(
						RefreshType.EVENT));
			}
		} else if (event instanceof PlayerLeaveEvent) {
			boolean changed = false;
			synchronized (sync) {
				ServerState state = serverState.freeze();
				String player = ((PlayerLeaveEvent) event).getPlayerName();
				PlayerState old = serverState.getPlayerList().byName()
						.get(player);
				if (old != null) {

					Iterable<PlayerState> filtered = filter(serverState
							.getPlayerList().getPlayers(),
							not(in(singleton(old))));
					ImmutableList<PlayerState> build = new ImmutableList.Builder<PlayerState>()
							.addAll(filtered).build();

					serverState.setPlayerList(new PlayerList(build));
				}
				changed = !serverState.equals(state);
			}
			if (changed) {
				eventManager.sendEvent(new StatsRefreshedEvent(
						RefreshType.EVENT));
			}
		}

	}

	public void init(final RCONClient client) {
		final CountDownLatch init = new CountDownLatch(1);
		scheduler.scheduleAtFixedRate(new Runnable() {

			@Override
			public void run() {
				List<PlayerInfo> playerInfo = null;
				ServerInfo serverInfo = null;
				try {
					{
						Future<Response> send = client
								.send(new ListPlayersRequest(PlayerSet.ALL));
						Response response = Futures.get(send, 5,
								TimeUnit.SECONDS, IOException.class);
						playerInfo = ((ListPlayersResponse) response)
								.getPlayerInfo();

					}
				} catch (Exception e) {
					e.printStackTrace();
				}
				try {
					{
						ServerInfoRequest serverInfoRequest = new ServerInfoRequest();
						Future<Response> send = client.send(serverInfoRequest);
						Response response = Futures.get(send, 5,
								TimeUnit.SECONDS, IOException.class);
						serverInfo = ((ServerInfoResponse) response)
								.getServerInfo();
					}
				} catch (Exception e) {
					e.printStackTrace();
				}
				update(serverInfo, playerInfo);
				init.countDown();
			}
		}, 0, 5, TimeUnit.SECONDS);

		Uninterruptibles.awaitUninterruptibly(init);
		LogFactory.getLog(getClass()).info("Server state initialized");

	}

	private void update(ServerInfo info, List<PlayerInfo> newPlayerInfo) {
		boolean changed = false;
		synchronized (sync) {
			ServerState state = serverState.freeze();
			if (newPlayerInfo != null) {
				List<PlayerState> playerslist = newArrayListWithExpectedSize(newPlayerInfo
						.size());
				for (PlayerInfo playerInfo : newPlayerInfo) {
					PlayerState player = serverState.getPlayerList().byName()
							.get(playerInfo.name);
					if (player == null) {
						player = new PlayerState(playerInfo);
					} else {
						player = player.update(playerInfo);
					}
					playerslist.add(player);
				}

				serverState.setPlayerList(new PlayerList(playerslist));
			}
			if (info != null) {
				serverState.setRound(serverState.getRound().update(info));
			}
			changed = !serverState.equals(state);
		}
		if (changed) {
			eventManager.sendEvent(new StatsRefreshedEvent(RefreshType.POLL));

		}
	}

}
